﻿using System;
using System.Collections.Specialized;
using System.IO;
using System.Web;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Web.SessionState;
using Enyim.Caching;
using Enyim.Caching.Memcached;
using ChunQiu.Arch.Session.Core.Models;

namespace ChunQiu.Arch.Session.Core
{
    /// <summary>
    /// 基于Memcached的Session提供程序
    /// </summary>
    public sealed class MemcachedSessionProvider : SessionStateStoreProviderBase
    {

        #region Ctors

        /// <summary>
        /// 
        /// </summary>
        public MemcachedSessionProvider()
        {
             client = new MemcachedClient();
        }

        #endregion

        #region Fields

        private IMemcachedClient client;
        private static readonly int defaultSessionExpireMinute = 20;
        private int timeout;

        #endregion

        #region Public Methods

        /// <summary>
        ///  初始化提供程序
        /// </summary>
        /// <param name="name"></param>
        /// <param name="config"></param>
        public override void Initialize(string name, NameValueCollection config)
        {
            var applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath;
            var webConfig = WebConfigurationManager.OpenWebConfiguration(applicationVirtualPath);
            var sessionStateSection = (SessionStateSection)webConfig.GetSection("system.web/sessionState");

            if (sessionStateSection == null || sessionStateSection.Timeout == null || sessionStateSection.Timeout.Minutes <= 0)
            {
                this.timeout = defaultSessionExpireMinute;
            }
            else
            {
                this.timeout = sessionStateSection.Timeout.Minutes;
            }
        }

        /// <summary>
        /// 创建要用于当前请求的新 System.Web.SessionState.SessionStateStoreData 对象
        /// </summary>
        /// <param name="context"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
        {
            return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), timeout);
        }

        /// <summary>
        /// 将新的会话状态项添加到数据存储区中
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="timeout"></param>
        public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
        {
            var session = new MemcachedSessionDo()
            {
                Content = null,
                Locked = false,
                SetTime = DateTime.Now,
                LockId = 0,
                Flag = 1
            };

            client.Store(Enyim.Caching.Memcached.StoreMode.Set, id, session, TimeSpan.FromMinutes(timeout));
        }

        /// <summary>
        /// 释放由 System.Web.SessionState.SessionStateStoreProviderBase 实现使用的所有资源
        /// </summary>
        public override void Dispose() { }

        /// <summary>
        /// 在请求结束时由 System.Web.SessionState.SessionStateModule 对象调用
        /// </summary>
        /// <param name="context"></param>
        public override void EndRequest(HttpContext context) { }

        /// <summary>
        /// 从会话数据存储区中返回只读会话状态数据
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="locked"></param>
        /// <param name="lockAge"></param>
        /// <param name="lockId"></param>
        /// <param name="actions"></param>
        /// <returns></returns>
        public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            return GetSessionStoreItem(false, context, id, out locked, out lockAge, out lockId, out actions);
        }

        /// <summary>
        /// 从会话数据存储区中返回只读会话状态数据
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="locked"></param>
        /// <param name="lockAge"></param>
        /// <param name="lockId"></param>
        /// <param name="actions"></param>
        /// <returns></returns>
        public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            return GetSessionStoreItem(true, context, id, out locked, out lockAge, out lockId, out actions);
        }

        /// <summary>
        /// 由 System.Web.SessionState.SessionStateModule 对象调用，以便进行每次请求初始化
        /// </summary>
        /// <param name="context"></param>
        public override void InitializeRequest(System.Web.HttpContext context) { }

        /// <summary>
        /// 释放对会话数据存储区中项的锁定
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="lockId"></param>
        public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
        {
            var session = client.Get(id) as MemcachedSessionDo;
            if (session != null)
            {
                session.Locked = false;
                session.LockId = (int)lockId;
                client.Store(StoreMode.Set, id, session, TimeSpan.FromMinutes(timeout));
            }
        }

        /// <summary>
        /// 删除会话数据存储区中的项数据
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="lockId"></param>
        /// <param name="item"></param>
        public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
        {
            client.Remove(id);
        }

        /// <summary>
        /// 更新会话数据存储区中的项的到期日期和时间
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        public override void ResetItemTimeout(HttpContext context, string id)
        {
            var obj = client.Get(id);
            if (obj != null)
            {
                client.Store(StoreMode.Set, id, obj, TimeSpan.FromMinutes(timeout));
            }
        }

        /// <summary>
        /// 使用当前请求中的值更新会话状态数据存储区中的会话项信息，并清除对数据的锁定。
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="item"></param>
        /// <param name="lockId"></param>
        /// <param name="newItem"></param>
        public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
        {
            var setTime = DateTime.Now;
            var bytes = Serialize((SessionStateItemCollection)item.Items);
            var session = new MemcachedSessionDo
            {
                LockId = 0,
                Locked = false,
                Content = bytes,
                Flag = 0,
                SetTime = setTime
            };
            client.Store(StoreMode.Set, id, session, TimeSpan.FromMinutes(item.Timeout));
        }

        /// <summary>
        /// 设置对 Global.asax 文件中定义的 Session_OnEnd 事件的 System.Web.SessionState.SessionStateItemExpireCallback
        /// </summary>
        /// <param name="expireCallback"></param>
        /// <returns></returns>
        public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
        {
            return false;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// 获取会话数据存储区中的项
        /// </summary>
        /// <param name="lockRecord"></param>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <param name="locked"></param>
        /// <param name="lockAge"></param>
        /// <param name="lockId"></param>
        /// <param name="actions"></param>
        /// <returns></returns>
        private SessionStateStoreData GetSessionStoreItem(bool lockRecord, System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
        {
            SessionStateStoreData sessionStateStoreData = null;
            MemcachedSessionDo session = null;
            DateTime setTime = DateTime.Now;

            lockAge = TimeSpan.Zero;
            lockId = null;
            locked = false;
            actions = SessionStateActions.None;
            session = client.Get(id) as MemcachedSessionDo;

            if (session != null)
            {
                //如果已经锁定
                if (session.Locked)
                {
                    lockAge = session.LockAge;
                    lockId = session.LockId;
                    locked = session.Locked;
                    actions = (SessionStateActions)session.Flag;
                    return sessionStateStoreData;
                }

                session.LockId++;
                session.SetTime = setTime;

                client.Store(Enyim.Caching.Memcached.StoreMode.Set, id, session);

                actions = (SessionStateActions)session.Flag;
                lockId = session.LockId;
                lockAge = session.LockAge;

                if (actions == SessionStateActions.InitializeItem)
                    sessionStateStoreData = this.CreateNewStoreData(context, timeout);
                else
                    sessionStateStoreData = Deserialize(context, session.Content, timeout);

                return sessionStateStoreData;
            }

            return sessionStateStoreData;
        }

        /// <summary>
        /// 序列化
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private byte[] Serialize(SessionStateItemCollection items)
        {
            var ms = new MemoryStream();
            var writer = new BinaryWriter(ms);

            if (items != null)
            {
                items.Serialize(writer);
            }

            writer.Close();

            return ms.ToArray();
        }

        /// <summary>
        /// 反序列化
        /// </summary>
        /// <param name="context"></param>
        /// <param name="content"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        private SessionStateStoreData Deserialize(HttpContext context, byte[] content, int timeout)
        {
            var stream = new MemoryStream(content);
            var collection = new SessionStateItemCollection();

            if (stream.Length > 0)
            {
                var reader = new BinaryReader(stream);
                collection = SessionStateItemCollection.Deserialize(reader);
            }

            return new SessionStateStoreData(collection, SessionStateUtility.GetSessionStaticObjects(context), timeout);
        }

        #endregion

    }
}
